Skip to content

Add project-scoped OAuth authorization - #138

Open
masnwilliams wants to merge 13 commits into
mainfrom
hypeship/project-scoped-oauth
Open

Add project-scoped OAuth authorization#138
masnwilliams wants to merge 13 commits into
mainfrom
hypeship/project-scoped-oauth

Conversation

@masnwilliams

@masnwilliams masnwilliams commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

summary

  • add an explicit authorization choice between organization-wide access and one active project
  • bind authorization context to PKCE requests, access tokens, and rotated refresh tokens with atomic Redis persistence
  • verify Clerk organization membership before refresh rotation, enrich legacy refresh context after validation, and reject client-supplied scope escalation
  • keep legacy org-only access and refresh mappings organization-wide while requiring S256 PKCE outside an explicit legacy allowlist
  • normalize project API transport and response failures as temporary upstream errors and keep the hosted OAuth UI explicitly light
  • add route-level coverage for registration, authorization, token exchange, refresh, project validation, concurrency boundaries, and negative cases

tests

  • bun test (175 passing)
  • targeted Prettier checks for changed files
  • bun run build with test build-time configuration

dependency

Requires kernel/kernel#3165 to be deployed before this change writes structured OAuth contexts.


Note

High Risk
Changes core OAuth authorize/token flows, Redis persistence shape, and scope enforcement; depends on kernel#3165 for structured contexts and misconfiguration could break login or widen/narrow access incorrectly.

Overview
Adds organization-wide vs single-project OAuth authorization, with project choice in /select-org (search/paginated via new GET /oauth/projects) and scope carried through authorize → token responses.

Authorization context replaces org-only Redis mappings: structured JSON (org + optional project_id) is stored on PKCE-bound requests, JWT/refresh tokens, and consumed atomically on code exchange. S256 PKCE is required for new clients and mandatory for project scope; OAUTH_LEGACY_NON_PKCE_CLIENT_IDS keeps org-only flows for allowlisted clients.

Token route resolves scope server-side (ignores client-supplied org_id / access_scope / project_id), checks Clerk org membership before refresh rotation, validates subject on auth-code grants, and returns org_id, access_scope, and project_id on issued tokens.

Docs/env note the legacy allowlist; layout forces light theme. Broad route and lib test coverage added.

Reviewed by Cursor Bugbot for commit 010e8a0. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mcp Ready Ready Preview Aug 11, 2026 4:10pm
mcp (staging) Ready Ready Preview Aug 11, 2026 4:10pm

Comment thread src/app/authorize/route.ts
Comment thread src/app/authorize/route.ts
Comment thread src/app/select-org/page.tsx
Comment thread src/app/select-org/page.tsx
Comment thread src/app/select-org/page.tsx
Comment thread src/lib/oauth-projects.ts
Comment thread src/app/token/route.ts
Comment thread src/app/token/route.ts
Comment thread src/app/token/route.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1d265ac. Configure here.

Comment thread src/app/select-org/page.tsx

@hiroTamada hiroTamada left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed the latest version — the atomic token persistence fix looks good. requesting changes for the first two OAuth correctness issues; the rest could be follow-ups.

bugs

  • src/app/token/route.ts:236-266,291-299 — after verifying a legacy refresh context, persist an enriched context containing payload.sub. currently the rotated token retains no clerk_user_id, so active legacy clients stay on the post-exchange membership path indefinitely and a transient failure can still burn their refresh chain.
  • src/app/authorize/route.ts:217-224 — non-PKCE organization grants store one context per public client_id, while src/lib/org-utils.ts:99-103 retrieves that last-write-wins value. concurrent users or flows can overwrite each other. can we restrict this compatibility path to an explicit legacy allowlist and require S256 PKCE for all other clients?

follow-ups

  • src/app/select-org/page.tsx:360-393,519-527 — the new scope picker hard-codes light surfaces while using dark-mode-aware text tokens, producing low-contrast text under system dark mode. use semantic surface and border tokens or explicitly keep this page light.
  • src/lib/oauth-projects.ts:60-63,106-109 and src/app/token/route.ts:188-209 — thread request cancellation and an explicit timeout through the new Kernel and Clerk calls so abandoned or stalled OAuth requests do not continue consuming server work.
  • src/lib/oauth-projects.ts:73,121 — normalize successful-response parse/schema failures and fetch exceptions to OAuthProjectsError(502); otherwise /authorize reports upstream failures as “project not found or inactive.”

@masnwilliams

Copy link
Copy Markdown
Collaborator Author

addressed both blocking OAuth correctness issues in 5970522:

  • successful validation now enriches legacy authorization contexts with the verified Clerk subject before persisting the access and rotated refresh mappings, so subsequent refreshes prevalidate membership before rotation
  • authorization and token exchange now require S256 PKCE unless the client ID appears in the explicit OAUTH_LEGACY_NON_PKCE_CLIENT_IDS allowlist; allowlisted fallback remains organization-wide only

added regression coverage for non-allowlisted clients, allowlisted organization grants, rejected project contexts, and legacy-context enrichment. bun test (173 passing) and bun run build pass.

Comment thread src/lib/oauth-projects.ts Outdated

@hiroTamada hiroTamada left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

latest fixes resolve the two OAuth correctness blockers. the remaining notes are non-blocking follow-ups. 173 tests and TypeScript pass locally.

@masnwilliams

Copy link
Copy Markdown
Collaborator Author

addressed the remaining review follow-ups that apply to this rollout:

  • 7540677/010e8a0: explicitly force the hosted OAuth UI to light theme, matching the existing Clerk appearance and removing the unreachable dark-mode mismatch
  • cf61c24: normalize project API transport, JSON, and schema failures to OAuthProjectsError(502), so /authorize returns a temporary 503 rather than a misleading access-denied response

request cancellation and timeout plumbing remains deferred as a separate reliability follow-up. bun test (175 passing) and bun run build pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants